package com.xc.quickstart.common.util;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import com.aliyun.openservices.shade.com.google.common.collect.Lists;
import com.xc.quickstart.common.annotation.Csv;
import com.xc.quickstart.common.model.CsvModel;
import com.xc.quickstart.service.dto.SampleCsvDTO;
import lombok.SneakyThrows;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

public class CsvUtil {

    public static CsvUtil newInstance() {
        return new CsvUtil('\t');
    }

    public static CsvUtil newInstance(char columnSeparator) {
        return new CsvUtil(columnSeparator);
    }

    private CsvUtil(char columnSeparator) {
        this.columnSeparator = columnSeparator;
    }

    private final char columnSeparator;

    private static final String CSV_ROW_SEPARATOR = "\r\n";

    private static <T> List<CsvModel> getCsvModelList(Class<T> clz) {
        List<CsvModel> csvModelList = Lists.newArrayList();
        Field[] fields = clz.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(Csv.class)) {
                Csv csv = field.getAnnotation(Csv.class);
                if (csv.ignore()) {
                    continue;
                }

                csvModelList.add(new CsvModel(StringUtils.isEmpty(csv.title()) ? field.getName() : csv.title(), csv.sort(), field, csv.format()));
                continue;
            }

            csvModelList.add(new CsvModel(field.getName(), 0, field, ""));
        }

        csvModelList.sort(Comparator.comparing(CsvModel::getSort));
        return csvModelList;
    }

    private static String upperCaseFirstChar(String content) {
        char[] chars = content.toCharArray();
        if (chars[0] >= 'a' && chars[0] <= 'z') {
            chars[0] = (char) (chars[0] - 32);
        }
        return new String(chars);
    }

    @SneakyThrows
    private <T> List<T> read(InputStream inputStream, Class<T> clz) {
        List<CsvModel> csvModelList = getCsvModelList(clz);
        String[] header = csvModelList.stream().map(CsvModel::getTitle).collect(Collectors.toList()).toArray(new String[]{});

        Map<String, Method> methodMap = Arrays.stream(clz.getDeclaredMethods()).collect(Collectors.toMap(Method::getName, method -> method));

        CSVFormat format = CSVFormat.DEFAULT
                .withHeader(header)
                .withSkipHeaderRecord()
                .withDelimiter(columnSeparator);

        Reader in = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
        CSVParser parser = format.parse(in);
        List<CSVRecord> records = parser.getRecords();

        List<T> list = Lists.newArrayList();

        for (CSVRecord record : records) {
            T t = clz.newInstance();

            for (CsvModel csvModel : csvModelList) {
                Field field = csvModel.getField();
                String title = csvModel.getTitle();
                Method method = methodMap.get("set" + upperCaseFirstChar(field.getName()));

                if (method == null) {
                    continue;
                }

                method.invoke(t, Convert.convert(field.getType(), record.get(title)));
            }

            list.add(t);
        }

        parser.close();
        in.close();

        return list;
    }

    @SneakyThrows
    public <T> void write(List<T> dataList, Class<T> clz, OutputStream os) {
        List<CsvModel> csvModelList = getCsvModelList(clz);
        StringBuffer buf = new StringBuffer();

        buf.append(csvModelList.stream().map(CsvModel::getTitle).collect(Collectors.joining(columnSeparator + "")));
        buf.append(CSV_ROW_SEPARATOR);

        writeBody(buf, csvModelList, dataList, clz);
        os.write(buf.toString().getBytes(StandardCharsets.UTF_8));
        os.flush();
    }

    @SneakyThrows
    private <T> void writeBody(StringBuffer buf, List<CsvModel> csvModelList, List<T> dataList, Class<T> clz) {
        Map<String, Method> methodMap = Arrays.stream(clz.getDeclaredMethods())
                .collect(Collectors.toMap(Method::getName, method -> method));

        if (!CollectionUtils.isEmpty(dataList)) {
            for (int i = 0; i < dataList.size(); i++) {
                T t = dataList.get(i);
                List<String> list = new ArrayList<>();
                for (CsvModel csvModel : csvModelList) {
                    Method method = methodMap.get("get" + upperCaseFirstChar(csvModel.getField().getName()));
                    if (method == null) {
                        list.add("");
                        continue;
                    }

                    Object value = method.invoke(t);
                    if (value == null) {
                        value = StringUtils.EMPTY;
                    }

                    if (value instanceof Date) {
                        SimpleDateFormat format = new SimpleDateFormat(StringUtils.isEmpty(csvModel.getFormat()) ? "yyyy-MM-dd HH:mm:ss" : csvModel.getFormat());
                        list.add(format.format(value));
                    } else {
                        list.add(String.valueOf(value));
                    }
                }

                buf.append(String.join(columnSeparator + "", list));

                if (i != dataList.size() - 1) {
                    buf.append(CSV_ROW_SEPARATOR);
                }
            }
        }
    }

    public static void main(String[] args) {
        BufferedInputStream inputStream = FileUtil.getInputStream("/Users/yinyansheng/Desktop/byq.tsv");

        List<SampleCsvDTO> read = CsvUtil.newInstance().read(inputStream, SampleCsvDTO.class);

        BufferedOutputStream outputStream = FileUtil.getOutputStream("/Users/yinyansheng/Desktop/byq22.tsv");
        CsvUtil.newInstance().write(read, SampleCsvDTO.class, outputStream);
    }

}
